#include <iostream>
#include <memory>
#include <vector>
#include <string>
#include <string>
#include <fstream>
#include <streambuf>
#include <sstream>
#include <cstdlib>
#include <new>
#include <typeinfo>
#include <list>
#include <thread> // std::thread
#include <mutex>  // std::mutex
#include <exception>

using namespace std;

// 参考: https://blog.csdn.net/misayaaaaa/article/details/53786215
// chapter 18  用于大型程序的工具

// 1.
// range_error r("error");
// throw r;  // 异常对象的类型是 range_error

// exception* p = &r;
// throw *p; // 异常对象的类型是 range_error, *p:解引用
// throw p; // 异常对象的类型是 exception

// 2.
// 知识点1：栈展开的过程中对象会被自动销毁（所在块中创建的对象），若是类类型，该对象的析构函数将会自动执行

// 知识点2：析构函数总会执行，但是函数中负责释放资源的代码可能会被跳过（异常发生在负责释放资源的代码之前），但是类分配的对象无论函数正常结束还是遭遇异常，
// 通过析构函数其资源都能够被正常释放（由于是自动执行的，所以析构函数中不应该抛出异常）

void exercise_1(int *b, int *e)
{
    vector<int> v(b, e);
    int *p = new int[v.size()];
    ifstream in("ints");
    // 如果此处发生异常,使得动态分配的数组没有被撤销,从而造成内存泄漏
    // new操作后的异常使得动态分配的数组没有被撤销
    // V会调用vector类的析构函数进行销毁，并释放相关内存，P指针会被销毁，但是P指针指向的内存由于是动态分配的，
    // 所以该内存不会被释放，造成内存泄漏。输入流对象会调用ifstream类的析构函数销毁，最后程序被终止
}

// 3.
// 要想让上面的代码在发生异常时能正常工作，有两种解决方案。请描述这两种方法并实现它们
// 方法一：不使用指针，使用对象
// 方法二：使用智能指针




// 使用try_catch 正确处理异常
void exercise(int *b, int *e)
{
    vector<int> v(b, e);
    int *p = new int[v.size()];
    try
    {
        ifstream in("ints"); // 此处发生异常
        range_error r("error");
        throw r;
    }
    catch (range_error &e)
    {
        delete p; //释放数组
    }
}

// 使用类封装数组  // 类的析构函数可以自动调用,释放内存
class Resource
{
public:
    Resource(size_t sz) : r(new int[sz]) {}

private:
    int *r;
};

void exercise_2(int *b, int *e)
{
    vector<int> v(b, e);
    Resource res(v.size());
    ifstream in("ints");
}

// 4.
// 若所个catch语句的类型之间存在这继承关系，应该将继承链的最低端的类放在前面
// catch语句应该从最低的派生类到最高的派生类

// try{

// } catch(overflow_error eobj){

// }catch(const runtime_error& re){

// }catch(exception)




// 5.
// 知识点：每个标准库异常类都包含了一个名为what的成员函数，这个函数没有参数，返回值是C风格的字符串（const char *）

// try
// {
//     //
// }
// catch (overflow_error e)
// {
//     cout << e.what();
//     abort();
// }
// catch (underflow_error u)
// {
//     cout << u.what();
//     abort();
// }
// catch (range_error r)
// {
//     cout << r.what();
//     abort();
// }
// catch (domain_error d)
// {
//     cout << d.what();
//     abort();
// }
// catch (invalid_argument i)
// {
//     cout << i.what();
//     abort();
// }
// catch (out_of_range o)
// {
//     cout << o.what();
//     abort();
// }
// catch (length_error l)
// {
//     cout << l.what();
//     abort();
// }
// catch (runtime_error r)
// {
//     cout << r.what();
//     abort();
// }
// catch (logic_error l)
// {
//     cout << l.what();
//     abort();
// }
// catch (bad_alloc b)
// {
//     cout << b.what();
//     abort();
// }
// catch (bad_alloc b)
// {
//     cout << b.what();
//     abort();
// }
// catch (exception e)
// {
//     cout << e.what();
//     abort();
// }



// 6.
// 使用throw时,异常对象的类型要和catch语句的捕获的对象类型对应

已知下面的异常类型和 catch 语句，书写一个 throw 表达式使其创建的异常对象能被这些 catch 语句捕获：

(a) class exceptionType { };
	catch(exceptionType *pet) { }
(b) catch(...) { }
(c) typedef int EXCPTYPE;
	catch(EXCPTYPE) { }
解：

(a): throw exceptionType();
(b): throw expection();
(c): EXCPTYPE e = 1; throw e;

(a)：throw &exceptionType()

(b)：任何异常皆可

(c)：throw int()




// 7.
//
// 知识点1：由于构造函数在其进入函数体之前进行初始化（执行初始化列表），所以此时的try语句块还未生效，
// 所以构造函数体内的catch语句无法处理构造函数初始值列表抛出的异常，这是就需要构造函数try语句块的形式，
// 即将初始值列表置于try语句块中（try语句块出现在：冒号之后以及函数体的花括号之前）
// 所有的构造函数都添加这个try语句块即可

template <typename T>
class Blob
{
public:
    Blob(); //构造函数
private:
    shared_ptr<vector<T>> data;
};

template <typename T>
Blob<T>::Blob()
try : data(make_shared<vector<T>>())
{
}
catch (bad_alloc &e)
{
    cerr << e.what() << endl;
}




// 8.
// 析构函数发生异常
class MyTest_Base
{
public:
    virtual ~MyTest_Base()
    {
        try
        {
            exception e;
            throw e;
        }
        catch (...)
        {
        }
    }
};



// 9.
// 知识点1：exception表示的含义是某处出错，但错误细节未知，what是一个虚函数，所以引用捕获时会发生动态绑定
// 知识点2：自己定义的异常类型和标准库的异常类处理方式完全一样，一处抛出一处捕获

struct out_of_stock : public std::runtime_error
{
    explicit out_of_stock(string &s) : std::runtime_error(s) {}
};

// 继承 std::logic_error
struct ISBN_mismatch : public std::logic_error
{
    explicit ISBN_mismatch(const string &s) : std::logic_error(s) {}
    ISBN_mismatch(const string &s, string &ls, string &rs) : logic_error(s), left(ls), right(rs) {}

    string left;
    string right;
};



// 10.

// 11.
// what函数是用来返回初始化异常对象信息的，若what中也要抛出异常，是不是就造成无限递归了

// 12.

// 13.
// 使用未命名的命名空间
// 希望所定义的对象、函数、类类型或其他实体，只在程序的一小段代码中可见，这样可以进一步的缓解名字空间的冲突。根据C++11标准，static定义静态变量的做法已取消，现在是定义一个全局的未命名的名字空间。
// 在未命名的名字空间中定义的变量都是静态的
// 需要定义一系列静态的变量的时候



// 14.

// 15.
一条using声明语句一次只引入命名空间的一个成员。
using 指示使得某个特定的命名空间中所有的名字都可见。
// 知识点2：命名空间别名，namespace MM = mathLib::MatrixLib，就可以用MM来代替了。一个命名空间可以由多个别名，所有别名等价
// 知识点3：using声明：一次只能引入命名空间的一个成员，其有效范围从using声明开始，到using声明所在的作用域结束为止
// 知识点4：using指示：与using声明类似，也可以使用其简写模式，using namespace XX，将XX命名空间的所有成员变成可见，作用域和using声明一致。using指示一般被看作是出现在最近的外层作用域中，若有同名冲突成员，应加限定符予以区分

// 16. 17.
// 注意using声明与指示的区别

namespace Exercise
{
int ivar = 0;
double dvar = 0;
const int limit = 1000;
} // namespace Exercise

int ivar = 0;
// using Exercise::ivar; // ivar重复定义



// 18.
已知有下面的 swap 的典型定义，当 mem1 是一个 string 时程序使用 swap 的哪个版本？如果 mem1 是 int 呢？说明在这两种情况下名字查找的过程。
template <typename T>
void swap(T v1, T v2)
{
    using std::swap;
    swap(v1.mem1, v2.mem2);
}
// 当参数为string时，会先在string类中查找swap函数，找到则不使用std版本的。若未int类型，则直接使用标准库版本的swap



// 19.

// 20.
namespace primerLib
{
void compute();
void compute(const void *);
} // namespace primerLib

using primerLib::compute;

void compute(int);                  // 完全匹配
void compute(double, double = 3.4); // 实参隐式转换为double
void compute(char *, char * = 0);   // 实参隐式转换为 void *

void f()
{
    // using primerLib::compute;
    // compute(0);
}

// 21.
// 多重继承

// class CADVehicle : public CAD, Vehicle
// {
// };

// 错误,list出现了两次
// class DBlList : public List, public List
// {
// };
// class iostream : public istream, public ostream
// {
// };

// 22.
class A
{
};
class B : public A
{
};
class C : private B
{
};
class X
{
};
class Y
{
};
class Z : public X, public Y
{
};
class MI : public C, public Z
{
};
// 构造函数执行顺序：A-B-C-X-Y-Z-MI

// 23.
// 可以令某个可访问基类的指针或引用直接指向一个派生类对象，但该指针只能访问其对应的基类部分或者基类的基类部分
<<<<<<< HEAD:c++_practice_题目/src/c++_prime_questions_chapter_18.cpp
class D : public X, public C { };
=======
class D : public X, public C { ... };
>>>>>>> 8ca32d5184cb852fee6cc29515709a7d218d67fc:c++_practice/src/c++_prime_questions_chapter_18.cpp
p *pd = new D;
(a) X *px = pd;
(b) A *pa = pd;
(c) B *pb = pd;
(d) C *pc = pd;
<<<<<<< HEAD:c++_practice_题目/src/c++_prime_questions_chapter_18.cpp

// 可以令某个可访问基类的指针或引用直接指向一个派生类对象，但该指针只能访问其对应的基类部分或者基类的基类部分
(a)：允许
(b)：允许
(c)：允许
=======


可以令某个可访问基类的指针或引用直接指向一个派生类对象，但该指针只能访问其对应的基类部分或者基类的基类部分



(a)：允许

(b)：允许

(c)：允许

>>>>>>> 8ca32d5184cb852fee6cc29515709a7d218d67fc:c++_practice/src/c++_prime_questions_chapter_18.cpp
(d)：允许

// 24

// 25
<<<<<<< HEAD:c++_practice_题目/src/c++_prime_questions_chapter_18.cpp
// 假设我们有两个基类 Base1 和 Base2 ，它们各自定义了一个名为 print 的虚成员和一个虚析构函数。从这两个基类中文名派生出下面的类，它们都重新定义了 print 函数：
=======
假设我们有两个基类 Base1 和 Base2 ，它们各自定义了一个名为 print 的虚成员和一个虚析构函数。从这两个基类中文名派生出下面的类，它们都重新定义了 print 函数：
>>>>>>> 8ca32d5184cb852fee6cc29515709a7d218d67fc:c++_practice/src/c++_prime_questions_chapter_18.cpp

class D1 : public Base1 { /* ... */};
class D2 : public Base2 { /* ... */};
class MI : public D1, public D2 { /* ... */};
通过下面的指针，指出在每个调用中分别使用了哪个函数：

Base1 *pb1 = new MI;
Base2 *pb2 = new MI;
D1 *pd1 = new MI;
D2 *pd2 = new MI;
(a) pb1->print();
(b) pd1->print();
(c) pd2->print();
(d) delete pb2;
(e) delete pd1;
(f) delete pd2;

这个题要注意，虽然是基类指针，但是绑定的是派生类的对象，虽然只可以访问基类部分的成员，但是这基类部分的成员也是属于派生类的，不要被迷惑了

(a)：调用MI的print()函数

(b)：调用MI的print()函数

(c)：调用MI的print()函数

如果我们可能会删除一个指向派生类的基类指针时，应该把析构函数声明为虚函数。 事实上，《Effective C++》中的观点是，只要一个类有可能会被其它类所继承， 就应该声明虚析构函数。



虚函数依赖虚函数表进行工作。如果一个类中，有函数被关键词virtual进行修饰， 那么一个虚函数表就会被构建起来保存这个类中虚函数的地址。同时， 编译器会为这个类添加一个隐藏指针指向虚函数表。如果在派生类中没有重写虚函数， 那么，派生类中虚表存储的是父类虚函数的地址。每当虚函数被调用时， 虚表会决定具体去调用哪个函数。因此，C++中的动态绑定是通过虚函数表机制进行的。

当我们用基类指针指向派生类时，虚表指针vptr指向派生类的虚函数表。 这个机制可以保证派生类中的虚函数被调用到。

(d)：调用MI的析构函数，再调用Base2的析构函数

(e)：调用MI的析构函数，再调用D2的析构函数，再调用Base2的析构函数，再调用D1的析构函数，再调用Base1的析构函数

(f)：调用MI的析构函数，再调用D2的析构函数，再调用Base2的析构函数，再调用D1的析构函数，再调用Base的1析构函数



// 26.
已知如上所示的继承体系，下面对print的调用为什么是错误的？适当修改MI，令其对print的调用可以编译通过并正确执行。

MI mi;
mi.print(42);

参数为int，调用会产生二义性，mi.Base1::print()，加上限定符即可，直接调用Base1的成员



// 27.
已知如上所示的继承体系，同时假定为MI添加了一个名为foo的函数：

int ival;
double dval;
void MI::foo(double cval)
{
	int dval;
	//练习中的问题发生在此处
}
(a) 列出在MI::foo中可见的所有名字。
(b) 是否存在某个可见的名字是继承自多个基类的？
(c) 将Base1的dval成员与Derived 的dval 成员求和后赋给dval的局部实例。
(d) 将MI::dvec的最后一个元素的值赋给Base2::fval。
(e) 将从Base1继承的cval赋给从Derived继承的sval的第一个字符

a、可以见的：
Base1：ival、dval、cval、print
Base2：fval、print
Derived：sval、dval、print
MI：ival、dvec、print、foo
还有在foo里定义的ival


b、是：
dval：Base1::dval、Derived::dval、自己定义的int dval
ival ：MI::ival、Base1::ival
cval：Base1::cval、形参cval
print：Base1:print、Base2::print、Derived::print、MI::print



// 28.
<<<<<<< HEAD:c++_practice_题目/src/c++_prime_questions_chapter_18.cpp
// 已知存在如下的继承体系，在 VMI 类的内部哪些继承而来的成员无须前缀限定符就能直接访问？
// 哪些必须有限定符才能访问？说明你的原因。
=======
已知存在如下的继承体系，在 VMI 类的内部哪些继承而来的成员无须前缀限定符就能直接访问？哪些必须有限定符才能访问？说明你的原因。
>>>>>>> 8ca32d5184cb852fee6cc29515709a7d218d67fc:c++_practice/src/c++_prime_questions_chapter_18.cpp

struct Base {
	void bar(int);
protected:
	int ival;
};
struct Derived1 : virtual public Base {
	void bar(char);
	void foo(char);
protected:
	char cval;
};
struct Derived2 : virtual public Base {
	void foo(int);
protected:
	int ival;
	char cval;
};
class VMI : public Derived1, public Derived2 { };


需要加限定符：foo()，cval

不需要加限定符：bar()，ival


知识点1：实际上派生类可能多次继承自一个相同的类（两个直接基类继承自一个相同的间接基类），造成的结果是派生类含有该基类的多个子对象
知识点2：虚继承的目的是令某个类做出声明，承诺愿意共享它的基类，共享的基类子对象称为及虚基类，在此情况下，无论虚基类在集成体系中出现多少次，派生类中都只包含唯一一个共享的虚基类对象

知识点3：具体做法是在继承说明中加上virtual，可以与集成说明符互换

知识点4：成员被基类多覆盖的情况需要分类讨论


// 29.
<<<<<<< HEAD:c++_practice_题目/src/c++_prime_questions_chapter_18.cpp
// 已知有如下所示的类继承关系:
class Class { };
class Base : public Class { };
class D1 : virtual public Base { };
class D2 : virtual public Base { };
class MI : public D1, public D2 { };
class Final : public MI, public Class { };
=======
已知有如下所示的类继承关系：

class Class { ... };
class Base : public Class { ... };
class D1 : virtual public Base { ... };
class D2 : virtual public Base { ... };
class MI : public D1, public D2 { ... };
class Final : public MI, public Class { ... };
>>>>>>> 8ca32d5184cb852fee6cc29515709a7d218d67fc:c++_practice/src/c++_prime_questions_chapter_18.cpp
(a) 当作用于一个Final对象时，构造函数和析构函数的执行次序分别是什么？
(b) 在一个Final对象中有几个Base部分？几个Class部分？
(c) 下面的哪些赋值运算符将造成编译错误？
Base *pb; Class *pc; MI *pmi; D2 *pd2;
(a) pb = new Class;
(b) pc = new Final;
(c) pmi = pb;
(d) pd2 = pmi;


<<<<<<< HEAD:c++_practice_题目/src/c++_prime_questions_chapter_18.cpp
// 知识点1：在虚派生中，虚基类由最底层的派生类初始化，否则将会在多条继承路径上被重复初始化
// 知识点2：只要我们能创建虚基类的派生类对象，该派生类的构造函数就必须初始化它的虚基类
// 知识点3：首先用最底层派生类的构造函数的初始值初始化该对象的虚基类子部分，
// 虚基类优先构造，析构函数执行顺序与构造函数相反

// 答案:
// 构造顺序：Class、Base、D1、D2、MI、Class
// 析构顺序相反

// 1个Base部分，2个Class部分
=======
知识点1：在虚派生中，虚基类由最底层的派生类初始化，否则将会在多条继承路径上被重复初始化

知识点2：只要我们能创建虚基类的派生类对象，该派生类的构造函数就必须初始化它的虚基类

知识点3：首先用最底层派生类的构造函数的初始值初始化该对象的虚基类子部分，虚基类优先构造，析构函数执行顺序与构造函数相反



答案：

构造顺序：Class、Base、D1、D2、MI、Class

析构顺序相反



1个Base部分，2个Class部分


>>>>>>> 8ca32d5184cb852fee6cc29515709a7d218d67fc:c++_practice/src/c++_prime_questions_chapter_18.cpp

(a)、错误，Class是Base的基类，而pb是Base类
(b)、正确，基类Class可以指向所有子类
(c)、错误、pb是Base类，MI是Base的子类
(d)、正确、D2是MI的基类

<<<<<<< HEAD:c++_practice_题目/src/c++_prime_questions_chapter_18.cpp


=======
>>>>>>> 8ca32d5184cb852fee6cc29515709a7d218d67fc:c++_practice/src/c++_prime_questions_chapter_18.cpp

// 30 派生类中的不同类型的构造函数
class Class
{
};
class Base : public Class
{
public:
    Base() : ival(0) {}
    Base(int i) : ival(i) {}
    Base(const Base &b) : ival(b.ival) {}

protected:
    int ival;
};

class D1 : virtual public Base
{
public:
    D1() : Base(0) {}
    D1(int i) : Base(i){};
    D1(const D1 &d) : Base(d) {}
};

class D2 : virtual public Base
{
public:
    D2() : Base(0) {}
    D2(int i) : Base(i){};
    D2(const D2 &d) : Base(d) {}
};

// class MI : public D1, public D2
// {
// public:
//     MI() : Base(0) {}
//     MI(int i) : Base(i), D1(i), D2(i) {}
// };

// class M1 : public MI, public Class
// {
// };

int main()
{

    printf("hello c++ \n");
}